查看原文
其他

ggbreak:你们要的坐标轴截断,它来了

Y叔叔 YuLabSMU 2022-09-20

坐标轴截断在生物医学论文中,是一种处理Figure的常用手段,由于数据的分布不呈现正态,有些数据特别大,而大部分的数据又比较小,这样画出来的图没法看,如果数据是呈现log normal分布的话,可以通过对数转换来解决,但数据不一定符合某种分布,通过数据转换不一定能解决问题,有时候数据只是单纯有些outliers而已。比如在系统发育树上,如果我们引入一个外类分支(outgroup),这个外类分支的支长就可能特别长(当然系统发育树本身可能某种原因也会有分支特别长的情况),这样对可视化带来很大的问题,树的整体会因为特别长的分支被压缩到无法看清主体的树结构。这种情况在别的图形中也是可能出现的,截断是一种很好的解决方案。

截断图如果通过操作数据来达到目标是不可取的,因为它只能针对简单的图,诸如柱状图等,对于复杂的图,因为数据变了,图就会变。所以最好的方法就是在原图上设置窗口,把中间要截掉的那块拿走,然后拼在一起,借由拿走的那块,为图的细节部分增加空间,所以本质上是拼图,而且使用拼图,对任何图形都可适用。

别人的解决方案都是简单粗暴地拼图,一拼图就死了,没拼之前,你可以继续加图层,但你看不清楚细节部分,你把图画出来一看,看不清楚你自己加的图层效果怎么样(比如说柱状图上加标记p值)。你截断图之后,看是看得清了,但你没法再加图层了,因为图被搞死了。

针对这样的问题,我们开发了ggbreak包,图一画,有outlier,先截断,然后在看清楚细节的情况下,可以继续用ggplot2的语法,继续搞事。这就是我想要的,完全与ggplot2兼容。

开发这个包源自于5月8号在校内杨老师组织的小型会议上,我讲了ggtree,朱老师问我有枝长很长的情况下,能不能截断。基本上我以前也是觉得坐标轴有啥好截断的,做个数据转换就行了(比如log),这一问,我想到了数据就单纯只是有outliers,而outliers是有意义的,这样子的话,截断确实是个好方法。然后在下一个教授演讲的时候,我大概想了一下,好用的包应该是完全与ggplot2兼容的。第二天早上参加答辩,下午晚上带娃,大概想清楚了该怎么样来设计,想法其实很简单,但抄作业容易,原创却难,我想如果我不是写了plotbbaplot两个包的话,应该也不会很快有想法。

第三天,早上花了两小时,把我的想法写出来,于是v 0.0.1出来了,奠定了包的基调和设计框架,这一点很重要,一个包的设计,才是包的品味,能干活不算什么,能让用户毫无学习成本,用着觉得爽,才是真的好。

然后就是让我的博士生徐双斌(就是ggtreeExtra的作者)接手支持多个断点,他顺手就支持了scale_x/y_reverse。于是v0.0.2上线CRAN

后面我还提了不少要求,都被一一实现了,于是v0.0.3上线,可以和大家见面了。可以说,麻雀虽小,五脏具全,虽然是干一点小事的包,但基本上能满足你所有的想像。

Gap plot:截断坐标轴

这是通常我们说的,中间砍掉一些的图,我们只提供两个标尺函数,scale_x_breakscale_y_break,设置断点,它拥有一堆特性。

1. 与ggplot2兼容

你可以继续加图层,应用主题,想干啥就干啥。

library(ggplot2)
library(ggbreak) 
library(patchwork)

set.seed(2019-01-19)
d <- data.frame(x = 1:20,
   y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)
 
p1 <- ggplot(d, aes(y, x)) + geom_col(orientation="y")
d2 <- data.frame(x = c(218), y = c(726), label = c("hello""world"))
p2 <- p1 + scale_x_break(c(717)) + 
  geom_text(aes(y, x, label=label), data=d2, hjust=1, colour = 'firebrick')  + 
  xlab(NULL) + ylab(NULL) + theme_minimal()

p1 + p2

2. 支持多个断点

p2 + scale_x_break(c(1821))

3. 支持子图放大缩小

p1 + scale_x_break(c(7, 17), scales = 1.5) + scale_x_break(c(18, 21), scales=2)

4. 支持反转坐标轴

g <- ggplot(d, aes(x, y)) + geom_col()
g2 <- g + scale_y_break(c(7, 17), scales = 1.5) +
scale_y_break(c(18, 21), scale=2) + scale_y_reverse()
g + g2

5. 支持坐标轴变换

诸如scale_x_log10scale_x_sqrt等坐标轴变换,都能够应用到截断图中

p2 <- p1 + scale_x_break(c(7, 17))
p3 <- p1 + scale_x_break(c(7, 17)) + scale_x_log10()
p2 + p3

6. 支持反转坐标

g + coord_flip() + scale_y_break(c(7, 18))

7. 支持分面

set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22),
group = c(rep("A", 10), rep("B", 10)),
face=c(rep("C", 5), rep("D", 5), rep("E", 5), rep("F", 5))
)

p <- ggplot(d, aes(x=x, y=y)) +
geom_col(orientation="x") +
scale_y_reverse() +
facet_wrap(group~.,
scales="free_y",
strip.position="right",
nrow=2
) +
coord_flip()
pg <- p +
scale_y_break(c(7, 17), scales="free") +
scale_y_break(c(19, 21), scales="free")
print(pg)

8. 兼容图例

pg <- pg + aes(fill=group)
print(pg)

9. 兼容所有的图标签

pg + labs(title="test title", subtitle="test subtitle", tag="A tag", caption="A caption") +
theme_bw() +
theme(
legend.position = c(0.8, 0.8),
strip.placement = "outside",
axis.title.x=element_text(size=10),
plot.title = element_text(size = 22),
plot.subtitle = element_text(size = 16),
plot.tag = element_text(size = 10),
plot.title.position = "plot",
plot.tag.position = "topright",
plot.caption = element_text(face="bold.italic"),

)

10. 支持设置子图的坐标轴tick labels

require(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22),
group = c(rep("A", 10), rep("B", 10))
)

p <- ggplot(d, aes(x=x, y=y)) +
scale_y_reverse() +
scale_x_reverse() +
geom_col(aes(fill=group)) +
scale_fill_manual(values=c("#00AED7", "#009E73")) +
facet_wrap(
group~.,
scales="free_y",
strip.position="right",
nrow=2
) +
coord_flip()

p +
scale_y_break(c(7, 10), scales=0.5, ticklabels=c(10, 11.5, 13)) +
scale_y_break(c(13, 17), scales=0.5, ticklabels=c(17, 18, 19)) +
scale_y_break(c(19,21), scales=1, ticklabels=c(21, 22, 23))

11. 兼容patchwork

library(patchwork)

set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)

p <- ggplot(d, aes(x, y)) + geom_col()
x <- p+scale_y_break(c(7, 17 ))

x + p

Wrap plot:切分长图

我们提供了scale_wrap()标尺函数,专门针对x轴的数据很长的情况下,为了打印在纸上,你的图通常是压缩得太厉害,看不清细节的,那么这个函数呢,把你的图切成几个等份,画成几行,这样让你能更容易地去阅读、解说图。

p <- ggplot(economics, aes(x=date, y = unemploy, colour = uempmed)) +
geom_line()

p + scale_wrap(n=4)

Cut plot:图切片

这里提供了scale_x_cutscale_y_cut把图切成几片,和Gap plot相比,它不截断,它只是切,切成子图后,子图可以缩放。我设计这个图的初衷在于,比如你画gwas,比如你画火山图,是不是可以切片,然后把p值显著的子图放大,这样你能看清楚细节,同时你要标记基因或者位点,有足够的空间,当然这功能现在还需要再提升,因为在切点的位置,会出现于两个子图中,以后再改改。

library(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)
p <- ggplot(d, aes(x, y)) + geom_col()
p + scale_y_cut(breaks=c(7, 18), which=c(1, 3), scales=c(3, 0.5))

Note

在Gap plot里介绍的特性,同时也应该是Wrap plot和Cut plot所支持的,如果不支持,请github上反馈,以前如果你觉得有一些细节上还没有处理好的,github上反馈,如果你有一些别的截断图的想法或需求的,github上反馈。欢迎勾搭,一切尽在github。

往期精彩

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存